author = "Peter J Usherwood"

Esta tutorial é um exemplo de um aplicação em Python padrão, sem pacotes não padrão. Porque de isto, este codigo não é o mais simples ou eficiente, mas é transparente e um bom ferremento para apreder ambos Python, e cassificadores de apredizado de máquina. Isto é um aplicação útil tambem, vai dar os mesmos resultados de outros pactoes.

A aplicação nos vamos criar aqui é uma árvore de classificação. Uma árvore de classificação é um modelo de apredizado de maquinas classical que é usado para prever a classe de algumas observaçoes. este tecnico é usado hoje em muitas apliçoes comercial. É um modelo que aprende sobre lendo muitos exemplos de dados onde a classe é conhecido para aprender as regras que assina os arquivos para uma classe.


In [1]:
from random import seed
from random import randrange
import random
from csv import reader
from math import sqrt
import copy

Carregando os dados e pre-procesamento

Antes que nos comecamos criando a nossa árvore de classificação nos precisamos criar algumas funçoes que vamos nos ajudar. Primeiro nos precisamos


In [2]:
# carregar o arquivo de CSV
def carregar_csv(nome_arquivo):
    dados = list()
    with open(nome_arquivo, 'r') as arquivo:
        leitor_csv = reader(arquivo)
        for linha in leitor_csv:
            if not linha:
                continue
            dados.append(linha)
    return dados
        
def str_coluna_para_int(dados, coluna):
    classe = [row[column] for row in dataset]
    unique = set(class_values)
    lookup = dict()
    for i, value in enumerate(unique):
        lookup[value] = i
    for row in dataset:
        row[column] = lookup[row[column]]
    return lookup

# Convert string column to float
def str_column_to_float(dataset, column):
    
    column = column
    dataset_copy = copy.deepcopy(dataset)
    
    for row in dataset_copy:
        row[column] = float(row[column].strip())
    
    return dataset_copy

In [3]:
# carregar os dados
arquivo = '../../data_sets/sonar.all-data.csv'
dados = carregar_csv(arquivo)

# converte atributos de string para números inteiros
for i in range(0, len(dados[0])-1):
    dados = str_column_to_float(dados, i)
    
dados_X = [linha[:-1] for linha in dados]
dados_Y = [linha[-1] for linha in dados]


---------------------------------------------------------------------------
FileNotFoundError                         Traceback (most recent call last)
<ipython-input-3-4a6e96b013de> in <module>()
      1 # carregar os dados
      2 arquivo = '../../data_sets/sonar.all-data.csv'
----> 3 dados = carregar_csv(arquivo)
      4 
      5 # converte atributos de string para números inteiros

<ipython-input-2-338084fc7cb0> in carregar_csv(nome_arquivo)
      2 def carregar_csv(nome_arquivo):
      3     dados = list()
----> 4     with open(nome_arquivo, 'r') as arquivo:
      5         leitor_csv = reader(arquivo)
      6         for linha in leitor_csv:

FileNotFoundError: [Errno 2] No such file or directory: '../../data_sets/sonar.all-data.csv'

Proximo nos precisamos fazer os dados tem a mesma quantidade de cada classe. Isso é importante por a maioria de classificadores em machine learning porque se não o classificador preveria o modo cada vez, porque isso daria a melhor precisão.

Tambem nos precisamos dividir os nossos dados dentro dois conjuntos: um conjunto de trem, e um conjunto de teste. Nos nao vamos olhar com o nosso conjunto de teste, vamos só usar esse no fim para dar uma precisão. O trem nos usaremos para treinar a nossa árvore. Normalmente vamos usar 80% dos nossos dados para a treinar, 20% para a provar.

Nos poderiamos fazer esses passos em o outro ordem, o resulto é mais ou menos a mesma.


In [4]:
def equilibrar_as_classes(dados_X, dados_Y):

    classes = set(dados_Y)
    
    conta_min = len(dados_Y)
    
    for classe in classes:
        conta = dados_Y.count(classe)
        if conta < conta_min:
            conta_min = conta
            
    dados_igual_X = []
    dados_igual_Y = []
    indíces = set()
    
    for classe in classes:
        
        while len(dados_igual_Y) < len(classes)*conta_min: 
            
            indíce = random.randint(0,len(dados_X)-1)
            classe = dados_Y[indíce]

            if (indíce not in indíces) and (dados_igual_Y.count(classe) < conta_min):

                indíces.update([indíce])
                dados_igual_X.append(dados_X[indíce])
                dados_igual_Y.append(dados_Y[indíce])
    
    return dados_igual_X, dados_igual_Y

def criar_divisão_trem_teste(dados_X, dados_Y, relação=.8):
    
    classes = set(dados_Y)
    n_classes = len(classes)
    
    trem_classe_tamanho = int((len(dados_Y)*relação)/n_classes)
    
    indíces_todo = set(range(len(dados_X)))
    indíces_para_escolher = set(range(len(dados_X)))
    indíces = set()
    
    trem_X = []
    trem_Y = []
    teste_X = []
    teste_Y = []
    
    while len(trem_Y) < trem_classe_tamanho*n_classes:

        indíce = random.choice(list(indíces_para_escolher))
        indíces_para_escolher.remove(indíce)
        classe = dados_Y[indíce]

        if (trem_Y.count(classe) < trem_classe_tamanho):
            
                indíces.update([indíce])
                trem_X.append(dados_X[indíce])
                trem_Y.append(dados_Y[indíce])
                
    indíces_teste = indíces_todo - indíces
    
    for indíce in indíces_teste:
        
        teste_X.append(dados_X[indíce])
        teste_Y.append(dados_Y[indíce])  
        
    return trem_X, trem_Y, teste_X, teste_Y

In [5]:
dados_igual_X, dados_igual_Y = equilibrar_as_classes(dados_X, dados_Y)
trem_X, trem_Y, teste_X, teste_Y = criar_divisão_trem_teste(dados_igual_X, dados_igual_Y, relação=.8)

Proximo nos podemos começar construir a nossa árvore.

A árvore vai funcionar de dividido os registros de dados dentro grupos onde a distribução de classes são distinto, ela vai fazer isso muitas vezes ate uma boa previsão pode feitado.

Cada vez a árvore faz uma divisão é chamado um nó.

Para fazeras divisões de um nó a árvore recebe um valor de dado, por uma característica, e olhando o que vai acontecer para a distribução dos classes se a árvore dividido todos os registros sobre essa valore, por esta característica. Se os classes sera em groupos com a diferencia maior, isso e bom. Para escolher qual valor de qual característica para usar, a árvore iterar atraves cada característica em cada registro dos dados. Então ela compara todas as divisões e eschole a melhor. A medida de quanto separado sáo os classes, é o gini indíce.

Para começar nos vamos criar a função que faz a divião por um nó, vamos chamar obter_melhor_divisão.


In [6]:
def obter_melhor_divisão(dados_X, dados_Y, n_características=None):
    """
    Obter a melhor divisão pelo dados
    
    :param dados_X: Lista, o conjuncto de dados
    :param dados_Y: Lista, os classes
    :param n_características: Int, o numero de características para usar, quando você está usando a árvore sozinha fica 
    esta entrada em None
    
    :return: dicionário, pela melhor divisáo, o indíce da característica, o valor para dividir, e os groupos de registors
    resultandos da divisão
    """
    
    classes = list(set(dados_Y)) #lista único de classes
    b_indíce, b_valor, b_ponto, b_grupos = 999, 999, 999, None
    
    """
    # Addicionar os classes (dados_Y) para os registros
    for i in range(len(dados_X)):
        dados_X[i].append(dados_Y[i])
        
    dados = dados_X
    """
    
    if n_características is None:
        n_características = len(dados_X) - 1
    
    # Faz uma lista de características únicos para usar
    características = list()
    while len(características) < n_características:
        indíce = randrange(len(dados_X[0]))
        if indíce not in características:
            características.append(indíce)
    
    
    for indíce in características:
        for registro in dados_X:
            grupos = tentar_divisão(indíce, registro[indíce], dados_X, dados_Y)
            gini = gini_indíce(grupos, classes)
            if gini < b_ponto:
                b_indíce, b_valor, b_ponto, b_grupos = indíce, registro[indíce], gini, grupos
                
    return {'indíce':b_indíce, 'valor':b_valor, 'grupos':b_grupos}


def tentar_divisão(indíce, valor, dados_X, dados_Y):
    """
    Dividir o dados sobre uma característica e o valor da caracaterística dele
    
    :param indíce: Int, o indíce da característica
    :param valor: Float, o valor do indíce por um registro
    :param dados_X: List, o conjuncto de dados
    :param dados_Y: List, o conjuncto de classes
    
    :return: esquerda, direitaç duas listas de registros dividou de o valor de característica
    """
    
    esquerda_X, esquerda_Y, direita_X, direita_Y = [], [], [], []
    
    for linha_ix in range(len(dados_X)):
        if dados_X[linha_ix][indíce] < valor:
            esquerda_X.append(dados_X[linha_ix])
            esquerda_Y.append(dados_Y[linha_ix])
        else:
            direita_X.append(dados_X[linha_ix])
            direita_Y.append(dados_Y[linha_ix])
            
    return esquerda_X, esquerda_Y, direita_X, direita_Y


def gini_indíce(grupos, classes):
    """
    Calcular o indíce-Gini pelo dados diversão
    
    :param grupos: O grupo de registros
    :param classes: O conjuncto de alvos
    
    :return: gini, Float a pontuação de pureza
    """
    
    grupos_X = grupos[0], grupos[2]
    grupos_Y = grupos[1], grupos[3]
    
    gini = 0.0
    for valor_alvo in classes:
        for grupo_ix in [0,1]:
            tomanho = len(grupos_X[grupo_ix])
            if tomanho == 0:
                continue
            proporção = grupos_Y[grupo_ix].count(classes) / float(tomanho)
            gini += (proporção * (1.0 - proporção))
            
    return gini

In [ ]:
Agora que nos podemos obter a melhor divisão uma vez, nos precisamos fazer isso muitas vezes, e volta a resposta da árvore

In [7]:
def to_terminal(grupo_Y):
    """
    Voltar o valor alvo para uma grupo no fim de uma filial
    
    :param grupo_Y: O conjuncto de classes em um lado de uma divisão
    
    :return: valor_de_alvo, Int 
    """
    
    valor_de_alvo = max(set(grupo_Y), key=grupo_Y.count)
    return valor_de_alvo
 
    
def dividir(nó_atual, profundidade_max, tamanho_min, n_características, depth):
    """
    Recursivo, faz subdivisões por um nó ou faz um terminal

    :param nó_atual: o nó estar analisado agora, vai mudar o root
    :param max_profundidade: Int, o número máximo de iterações
    """
    
    esquerda_X, esquerda_Y, direita_X, direita_Y = nó_atual['grupos']
    del(nó_atual['grupos'])
    
    # provar por um nó onde um dos lados tem todos os dados
    if not esquerda_X or not direita_X:
        nó_atual['esquerda'] = nó_atual['direita'] = to_terminal(esquerda_Y + direita_Y)
        return
    
    # provar por profundidade maximo
    if depth >= profundidade_max:
        nó_atual['esquerda'], nó_atual['direita'] = to_terminal(esquerda_Y), to_terminal(direita_Y)
        return
    
    # processar o lado esquerda
    if len(esquerda_X) <= tamanho_min:
        nó_atual['esquerda'] = to_terminal(esquerda_Y)
    else:
        nó_atual['esquerda'] = obter_melhor_divisão(esquerda_X, esquerda_Y, n_características)
        dividir(nó_atual['esquerda'], max_depth, min_size, n_características, depth+1)

    # processar o lado direita
    if len(direita_X) <= tamanho_min:
        nó_atual['direita'] = to_terminal(direita_Y)
    else:
        nó_atual['direita'] = obter_melhor_divisão(direita_X, direita_Y, n_características)
        dividir(nó_atual['direita'], max_depth, min_size, n_características, depth+1)

Finalmente nos criamos uma função simples que nos vamos excecutar para criar a árvore.


In [8]:
def criar_árvore(trem_X, trem_Y, profundidade_max, tamanho_min, n_características):
    """
    Criar árvore
    
    :param:
    
    """
    root = obter_melhor_divisão(trem_X, trem_Y, n_características)
    dividir(root, profundidade_max, tamanho_min, n_características, 1)
    return root

E carrega!


In [9]:
n_características = len(dados_X[0])-1
profundidade_max = 10
tamanho_min = 1

árvore = criar_árvore(trem_X, trem_Y, profundidade_max, tamanho_min, n_características)

Agora nos podemos usar a nossa árvore para prever a classe de dados.


In [10]:
def prever(, linha):
    if linha[['indíce']] < ['valor']:
        if isinstance(['esquerda'], dict):
            return prever(['esquerda'], linha)
        else:
            return ['esquerda']
    else:
        if isinstance(['direita'], dict):
            return prever(['direita'], linha)
        else:
            return ['direita']

Agora nos podemos fazer preveções usando a nossa função prever, é melhor se nos usamos registros no nosso conjuncto de teste porque a árvore nao viu essas antes. Nos podemos fazer uma previção e depois comparar o resulto para a classe atual.


In [14]:
teste_ix = 9

print('A classe preveu da árvore é: ', str(prever(árvore, teste_X[teste_ix])))
print('A classe atual é: ', str(teste_Y[teste_ix][-1]))


A classe preveu da árvore é:  R
A classe atual é:  M

Proximo nos vamos criar uma função que vai comparar tudos os registros no nosso conjunto de teste e da a precisão para nos. A precisão é definido de o por cento a árvore preveu corrigir.


In [12]:
def precisão(teste_X, teste_Y, árvore):
    
    pontos = []
    
    for teste_ix in range(len(teste_X)):
        preverção = prever(árvore, teste_X[teste_ix])
        if preverção == teste_Y[teste_ix]:
            pontos += [1]
        else:
            pontos += [0]
            
    precisão_valor = sum(pontos)/len(pontos)
            
    return precisão_valor, pontos

In [13]:
precisão_valor = precisão(teste_X, teste_Y, árvore)[0]
precisão_valor


Out[13]:
0.625